﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class Board : MonoBehaviour {

    [SerializeField]
    // 用来生成的拼图
    private GameObject piecePrefab;

    [SerializeField]
    private UIManager uiManager;

    // 棋盘是二维的 有长和高， 我们使用二维数组
    // board[x,y] x 是横坐标，y是纵坐标
    private Piece[, ] board;

    // 棋盘的宽度
    private int width = 6;

    // 棋盘的高度
    private int height = 5;

    // 交换拼图的动画间隔
    private const float SwitchPieceCuration = 0.1f;

    // 宝珠掉落动画的速度
    public float FillPieceDuration = 0.2f;

    // 动画管理者（让移动的动画宝珠入列）
    [SerializeField]
    private TweenAnimationManager animManager;

    // 棋子的宽高(因为是圆形)
    public int pieceWidth;

    // 输入事件捕获
    // EventSystem eventSystem;

    // 获取点击向UI的射线
    /*
    用于检测投向Canvas的射线。Graphic Raycaster既可以配置忽略背面朝上的图形，也可以被UI元素前的2D和3D对象遮挡。
    参数：
        Ignore Reversed Graphics：忽略颠倒的图形的射线检测，即旋转180°后的图片不会与射线发生交互（检测）
        Blocked Objects：会阻挡图形射线的对象类型（2D或(/和)3D对象，需要有collider组件）。
        Blocked Mask：会阻挡图形射线的Layer。
    */

    [SerializeField]
    public GraphicRaycaster raycastInCanvas;

    // 拼图的下落动画
    private List<AnimData> fillPieceAnim = new List<AnimData> ();

    // 生成新拼图的时的坐标
    private List<Vector2> pieceCreatePos = new List<Vector2> ();

    // 拼图相邻的四个方向
    private Vector2[] directions = {
        Vector2.up,
        Vector2.down,
        Vector2.left,
        Vector2.right
    };

    // 棋盘的初始化
    public void InitializeBoard (int boardWidth, int boardHeight) {
        width = boardWidth;
        height = boardHeight;

        // 初始化棋盘
        board = new Piece[width, height];

        // 计算棋子的宽度(宽度为屏幕宽度/每一行棋子数)
        pieceWidth = Screen.width / boardWidth;

        // 遍历宽高 生成所有拼图
        for (int i = 0; i < boardWidth; i++) {
            for (int j = 0; j < boardHeight; j++) {

                // Debug.Log("i:"+ i + ",j:"+ j);
                CreatePiece (new Vector2 (i, j));
            }
        }

        // 播放下落动画
        animManager.AddListAnimData (fillPieceAnim);
    }

    // 分离拼图Prefab的Instantiate生成部分
    // 生成拼图对象
    public Piece InstantiatePiece (Vector3 createPos) {
        var piece = Instantiate (piecePrefab, createPos, Quaternion.identity).GetComponent<Piece> ();
        piece.transform.SetParent (transform);
        return piece;
    }

    //-------------------------------------------------------
    // Private Function
    //-------------------------------------------------------
    // 指定位置生成棋子 ver.1
    // private void CreatePiece (Vector2 position) {
    //     // 获取生成棋子位置的世界坐标
    //     var createPos = GetPieceWorldPos (position);

    //     // 生成拼图、生成后作为棋盘的子对象
    //     // ver.1
    //     // var piece = Instantiate (piecePrefab, createPos, Quaternion.identity).GetComponent<Piece> ();

    //     // 将新生成的棋子作为子元素放置到该对象下（作为UI Canvas的子对象）
    //     // piece.transform.SetParent (transform);

    //     var piece = InstantiatePiece (createPos);
    //     // 设置大小（就是屏幕的宽度/数量）
    //     piece.SetSize (pieceWidth);

    //     // 随机决定生成拼图的颜色（种类）
    //     // Enum.GetNames 需要引入 using System;
    //     // Enum.GetNames (typeof (PieceKind)).Length 获取枚举型的长度 这里有6种所以为6;
    //     PieceKind kind = (PieceKind) UnityEngine.Random.Range (0, Enum.GetNames (typeof (PieceKind)).Length);
    //     // 设置随机生成出来的种类
    //     piece.SetKind (kind);

    //     // 登录棋盘的情报
    //     board[(int) position.x, (int) position.y] = piece;

    //     // 登录动画
    //     fillPieceAnim.Add (new AnimData (piece.gameObject, createPos, FillPieceDuration));
    // }

    //-------------------------------------------------------
    // Private Function
    //-------------------------------------------------------
    // 指定位置生成拼图
    private void CreatePiece (Vector2 position) {

        // 生成拼图位置的坐标（相对坐标，最高一行之上一行处）
        var createPos = new Vector2 (position.x, height);

        // 如果该位置已经有拼图了，生成位置向上加一格
        while (pieceCreatePos.Contains (createPos)) {
            // 有的话往上顶一格，没有的话就不继续往上顶了
            createPos += Vector2.up;
        }

        // 登记生成坐标
        pieceCreatePos.Add (createPos);

        // 转成世界坐标
        var pieceCreateWorldPos = GetPieceWorldPos (createPos);

        // 随机决定生成拼图的颜色（种类）
        var kind = (PieceKind) UnityEngine.Random.Range (0, Enum.GetNames (typeof (PieceKind)).Length);

        // 生成拼图、生成后作为棋盘的子对象
        var piece = Instantiate (piecePrefab, pieceCreateWorldPos, Quaternion.identity).GetComponent<Piece> ();
        piece.transform.SetParent (transform);
        piece.SetSize (pieceWidth);
        piece.SetKind (kind);

        // 登录棋盘的情报
        board[(int) position.x, (int) position.y] = piece;

        // 生成后拼图的位置（相对坐标转世界坐标） 真实位置
        var piecePos = GetPieceWorldPos (position);

        // 登录动画
        fillPieceAnim.Add (new AnimData (piece.gameObject, piecePos, FillPieceDuration));
    }

    // 从棋盘上的位置上获取拼图对象的世界坐标
    private Vector3 GetPieceWorldPos (Vector2 boardPos) {
        // 增加宽度的一半用于使棋子位于中间而不是半个身子在屏幕外面
        return new Vector3 (boardPos.x * pieceWidth + (pieceWidth / 2), boardPos.y * pieceWidth + (pieceWidth / 2), 0);
    }

    // 寻找第一个点击的拼图
    public Piece GetStartPiece (Vector3 input) {

        // 通过eventSystem生成点击事件
        PointerEventData eventData = new PointerEventData (EventSystem.current);
        // 赋予点击事件的坐标
        eventData.pressPosition = Input.mousePosition;
        eventData.position = Input.mousePosition;
        // 获取鼠标点击以及之后移动后所选择到的物体
        // 收纳在这个list里
        List<RaycastResult> list = new List<RaycastResult> ();
        raycastInCanvas.Raycast (eventData, list);
        if (list.Count > 0) {
            for (int i = 0; i < list.Count; i++) {
                // Debug.Log (list [i].gameObject.name);
                // 寻找到被点击到的拼图并返回
                if (list[i].gameObject.name.Contains ("Piece")) {
                    return list[i].gameObject.GetComponent<Piece> ();
                }
            }
        }

        return null;

    }

    /*/// <summary>
    /// 从点击(触摸)输入位置的为止寻找与其相离最近的拼图 Ver.1 视觉上的最近，不是棋盘逻辑上的
    /// </summary>
    /// <param name="input">鼠标点击的输入位置</param>
    /// <returns></returns>
    public Piece GetNearestPiece (Vector3 input) {

        // 最小距离(初始化无限大)
        var minDist = float.MaxValue;
        // 相邻的拼图
        Piece nearestPiece = null;

        // 输入的位置与棋盘里所有的拼图的位置进行计算、寻找离输入最近的拼图
        foreach (var p in board) {
            // 两点之间的距离
            var dist = Vector3.Distance (input, p.transform.position);
            if (dist < minDist) {
                // 如果当前拼图与输入的距离更短，更新最短距离
                minDist = dist;
                nearestPiece = p;
            }
        }

        return nearestPiece;
    }*/

    // 从点击(触摸)输入位置的为止寻找与其相离最近的拼图
    public Piece GetNearestPiece (Vector3 input) {
        // 计算鼠标点击的位置相对是第几格
        var x = Mathf.Min ((int) (input.x / pieceWidth), width - 1);
        var y = Mathf.Min ((int) (input.y / pieceWidth), height - 1);
        return board[x, y];
    }

    /// <summary>
    /// 交换棋盘上的拼图
    /// </summary>
    /// <param name="p1">拼图1（正在控制中的拼图）</param>
    /// <param name="p2">拼图2（离得最近的拼图)</param>
    public void SwitchPiece (Piece p1, Piece p2) {
        // 移动位置（视觉上的位移） 
        /* ver.1 瞬间移动版本
        var p1Position = p1.transform.position;
        p1.transform.position = p2.transform.position;
        p2.transform.position = p1Position;*/

        // ver.2 有移动动画的版本
        var animList = new List<AnimData> ();
        // 拼图一移动到拼图二
        animList.Add (new AnimData (p1.gameObject, GetPieceWorldPos (GetPieceBoardPos (p2)), SwitchPieceCuration));
        // 拼图二移动到拼图一
        animList.Add (new AnimData (p2.gameObject, GetPieceWorldPos (GetPieceBoardPos (p1)), SwitchPieceCuration));

        animManager.AddListAnimData (animList);

        // 获取棋盘中的相对位置（通过视觉上的位置获取在棋盘中的位置，寻找这个对象的横与纵坐标）
        var p1BoardPos = GetPieceBoardPos (p1);
        var p2BoardPos = GetPieceBoardPos (p2);

        // 更新棋盘上的数据（更新在二维数组里的实例）
        board[(int) p1BoardPos.x, (int) p1BoardPos.y] = p2;
        board[(int) p2BoardPos.x, (int) p2BoardPos.y] = p1;
    }

    // 返回拼图在棋盘上处于什么位置
    private Vector2 GetPieceBoardPos (Piece piece) {
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (board[i, j] == piece) {
                    return new Vector2 (i, j);
                }
            }
        }

        return Vector2.zero;
    }

    // 判断棋盘上是否有配对的拼图
    public bool HasMatch () {
        foreach (var piece in board) {
            if (IsMatchPiece (piece)) {
                return true;
            }
        }
        return false;
    }

    // 当前的拼图是否被匹配了
    private bool IsMatchPiece (Piece piece) {
        // 获取拼图的信息
        // 获取相对位置
        var pos = GetPieceBoardPos (piece);
        // 种类
        var kind = piece.GetKind ();

        // 判定纵方向是否匹配中 
        // 判断当前种类的拼图，在上面方向一格 是不是与自己相同，相同的话数量自增
        // Vector2.up = new Vecetor(0, 1);
        var verticalMatchCount =
            GetSameKindPieceNum (kind, pos, Vector2.up) +
            // 判断当前种类的拼图，在下面方向一格 是不是与自己相同，相同的话数量自增
            // Vector2.up = new Vecetor(0, 1);
            GetSameKindPieceNum (kind, pos, Vector2.down)
            // 注: 把自己也要计算进去 所以要加1
            +
            1;

        // 判定横方向是否匹配中 注: 把自己也要计算进去 所以要加1
        var horizontalMatchCount = GetSameKindPieceNum (kind, pos, Vector2.right) + GetSameKindPieceNum (kind, pos, Vector2.left) + 1;

        // 纵方向 大于等于3  横方向大于等于3个相同拼图
        // 或者
        // 横方向大于等于3个相同拼图
        return verticalMatchCount >= GameManager.MachingCount || horizontalMatchCount >= GameManager.MachingCount;

        // 兼容超过三个拼图以上的时候L型消除
        // return verticalMatchCount + horizontalMatchCount >= GameManager.MachingCount;

    }

    /// <summary>
    /// 用参数来计算指定的方向里相同种的拼图有多少个
    /// </summary>
    /// <param name="kind">种类</param>
    /// <param name="piecePos">当前位置</param>
    /// <param name="searchDir">搜索方向</param>
    /// <returns>同种类拼图的个数</returns>
    private int GetSameKindPieceNum (PieceKind kind, Vector2 piecePos, Vector2 searchDir) {
        var count = 0;
        while (true) {
            // 下一个拼图的位置
            piecePos += searchDir;
            // 利用IsInBoard判断 防止超出棋盘越界 
            if (IsInBoard (piecePos) && board[(int) piecePos.x, (int) piecePos.y].GetKind () == kind) {
                count++;
            } else {
                break;
            }
        }
        return count;
    }

    // 对象的坐标在棋盘中是否存在(有没有出界)
    private bool IsInBoard (Vector2 pos) {

        /*
        // 在行范围中
        bool isInRow = (pos.x >= 0) && (pos.x < width);
        // 在列范围中
        bool isInCol = (pos.y >= 0) && (pos.y < height);
        return isInRow && isInCol;
        */

        return pos.x >= 0 && pos.y >= 0 && pos.x < width && pos.y < height;
    }

    // 删除匹配中的拼图 Ver.1
    /*public void DeleteMatchPiece ( ) {
        // 匹配中的拼图全部标记上删除flag
        foreach (var piece in board) {
            piece.deleteFlag = IsMatchPiece (piece);
        }

        // 删除掉被标记了删除flag的拼图
        foreach (var piece in board) {
            if (piece != null && piece.deleteFlag) {
                // 因为Destroy是非同步代码，要判断当前拼图是否是已经被删除了
                Destroy (piece.gameObject);
            }
        }
    }*/

    // Action 匿名函数类 Action可以是一个方法(函数)
    /*public void action () {
    }

    public Action demo_action;
    public void setAction () {
        demo_action = action;
    }*/

    // 带有参数的匿名函数
    /*private Action<int> intParamAction;
    private Action<int,string，Piece> intParamAction;

    private void withInt (int i) {

    }
    public void action () { }
    public void setAction () {
        intParamAction = withInt;
    }*/

    // 删除匹配中的拼图ver.2 (有间隔，删除拼图)
    /*public IEnumerator DeleteMatchPiece (Action endCallBadk) {
        // 匹配中的拼图全部标记上删除flag
        foreach (var piece in board) {
            piece.deleteFlag = IsMatchPiece (piece);
        }

        // 删除掉被标记了删除flag的拼图
        foreach (var piece in board) {
            if (piece != null && piece.deleteFlag) {
                Destroy (piece.gameObject);
            }
        }

        // 等待一秒钟间隔
        yield return new WaitForSeconds (1f);

        // 执行回调（迁移至掉落拼图的处理）
        endCallBadk ();

    }    */

    // 删除匹配中的拼图ver.3 (有间隔，分批次删除拼图)
    public IEnumerator DeleteMatchPiece (Action endCallBadk) {
        // 匹配中的拼图全部标记上删除flag
        foreach (var piece in board) {
            // 分批次配对
            if (piece != null && IsMatchPiece (piece)) {
                // 寻找正在配对中拼图的相对坐标
                var pos = GetPieceBoardPos (piece);
                DestroyMatchPiece (pos, piece.GetKind ());
                // 让UI增加Combo
                uiManager.AddCombo ();
                yield return new WaitForSeconds (0.5f);
            }
        }

        // 执行回调（迁移至掉落拼图的处理）
        endCallBadk ();

        // 虽然不用写 当协程找不到下一个yield返回值的时候，会执行break退出协程
        // yield break;

    }

    // 指定的拼图正在配对的场合、将其他配对的拼图同时删除
    private void DestroyMatchPiece (Vector2 pos, PieceKind kind) {
        // 拼图的位置在棋盘之外的时候 停止执行
        if (!IsInBoard (pos)) {
            return;
        }

        // 拼图无效或者被删除的flag、以及种类不同的时候什么也不执行
        // 递归判断的时候，这里是下一个拼图
        var piece = board[(int) pos.x, (int) pos.y];
        if (piece == null || piece.deleteFlag || piece.GetKind () != kind) {
            return;
        }

        // 即使拼图种类相同，如果没有被匹配，什么都不执行
        if (!IsMatchPiece (piece)) {
            return;
        }

        // 竖起删除flag、遍历周围四个方向递归判定
        piece.deleteFlag = true;
        foreach (var dir in directions) {
            // 递归，看匹配中的拼图是否还有被匹配的拼图
            // 如果还有接着标记删除
            DestroyMatchPiece (pos + dir, kind);
        }

        // 删除拼图 ver.1 瞬间删除
        // Destroy (piece.gameObject);

        // 删除拼图 ver.2 带透明动画效果的删除
        var tweenAlpha = piece.gameObject.AddComponent<AlphaTween> ();
        tweenAlpha.DoTween (1, 0, 0.3f, () => Destroy (piece.gameObject));

        

    }

    // 填满拼图被消失的位置、生成新的拼图 ver.1
    // public void FillPiece () {
    //     // 从下至上遍历棋盘中的拼图
    //     for (int i = 0; i < width; i++) {
    //         for (int j = 0; j < height; j++) {
    //             FillPiece (new Vector2 (i, j));
    //         }
    //     }
    // }

    // 填满拼图被消失的位置、生成新的拼图 ver.2
    /*public IEnumerator FillPiece (Action endCallBadk) {
        // 从下至上遍历棋盘中的拼图
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                FillPiece (new Vector2 (i, j));
            }
        }

        yield return new WaitForSeconds (1.0f);
        endCallBadk ();
    }*/

    // 填满拼图被消失的位置、生成新的拼图 ver.3
    // 掉落的补充动画，还是利用MoveTween进行动画，用AnimData交换宝珠的动画记录坐标
    public IEnumerator FillPiece (Action endCallBadk) {

        // 生成坐标清空
        pieceCreatePos.Clear ();

        // 掉落动画清空
        fillPieceAnim.Clear ();

        // 从下至上遍历棋盘中的拼图
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                // 掉落中会生成生成坐标，并填充到pieceCreatePos里
                // 掉落中会生成掉落动画，并填充到fillPieceAnim里
                FillPiece (new Vector2 (i, j));
            }
        }
        // 播放动画
        animManager.AddListAnimData (fillPieceAnim);

        yield return new WaitForSeconds (1.0f);
        endCallBadk ();
    }

    // 判断指定的拼图有没有被删除、执行删除之后是进行填充还是生成新的
    private void FillPiece (Vector2 pos) {

        // 当前选择的拼图
        var piece = board[(int) pos.x, (int) pos.y];
        if (piece != null && !piece.deleteFlag) {
            // 拼图如果没被删除，什么也都不发生
            return;
        }
        // 被删除之后的处理:
        // 确认对象拼图的上方向里有没有有効（没被标记删除且没被删除）的拼图、有的话进行移动
        // 上面一格
        var checkPos = pos + Vector2.up;
        // 上一个格子是否在棋盘之中
        while (IsInBoard (checkPos)) {
            // 检查上一格棋子
            var checkPiece = board[(int) checkPos.x, (int) checkPos.y];
            // 如果没有被删除或者被标记删除
            if (checkPiece != null && !checkPiece.deleteFlag) {
                // 这里处理的是掉落
                // 让上一格的视觉坐标位置移动到当前格（没有动画补件）
                // checkPiece.transform.position = GetPieceWorldPos (pos);
                // 填充生成位置到下落位置的补间动画
                fillPieceAnim.Add (new AnimData (checkPiece.gameObject, GetPieceWorldPos (pos), FillPieceDuration));
                // 交换棋盘里的数据
                board[(int) pos.x, (int) pos.y] = checkPiece;
                board[(int) checkPos.x, (int) checkPos.y] = null;
                return;
            }
            checkPos += Vector2.up;
        }

        // 遍历到最顶部没拼图，在当前坐标进行创建
        CreatePiece (pos);
    }
}